#include "light.h"
#include <cstdio>

Light::Light()
{
	pos[0]=pos[1]=pos[2]=dir[0]=dir[1]=0;
	dir[2]=0;
	
	genFBO();
}

Light::Light(float px, float py, float pz, float dx, float dy, float dz)
{
	pos[0]=px;
	pos[1]=py;
	pos[2]=pz;
	dir[0]=dx;
	dir[1]=dy;
	dir[2]=dz;
	
	genFBO();
}

void Light::setPosition(float x, float y, float z)
{
	pos[0] = x;
	pos[1] = y;
	pos[2] = z;
}

GLuint Light::getDepthId()
{
	return depth;
}

void Light::genFBO()
{
	GLenum FBOstatus;
	
	// Try to use a texture depth component
	glGenTextures(1, &depth);
	glBindTexture(GL_TEXTURE_2D, depth);
	
	// GL_LINEAR does not make sense for depth texture. However, next tutorial shows usage of GL_LINEAR and PCF
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	//glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB);
	
	// Remove artefact on the edges of the shadowmap
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
	
	// No need to force GL_DEPTH_COMPONENT24, drivers usually give you the max precision if available 
	glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
	//glBindTexture(GL_TEXTURE_2D, 0);
	
	// create a framebuffer object
	glGenFramebuffersEXT(1, &fbo);

	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
	
	// Instruct openGL that we won't bind a color texture with the currently binded FBO
	glDrawBuffer(GL_NONE);
	glReadBuffer(GL_NONE);
	
	// attach the texture to FBO depth attachment point
	glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,GL_TEXTURE_2D, depth, 0);
	
	// check FBO status
	FBOstatus = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
	if(FBOstatus != GL_FRAMEBUFFER_COMPLETE_EXT)
		printf("GL_FRAMEBUFFER_COMPLETE_EXT failed, CANNOT use FBO\n");
	
	// switch back to window-system-provided framebuffer
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
}

void Light::setAsPOV()
{
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	//gluPerspective(60, (float)SHADOW_WIDTH/SHADOW_HEIGHT, 50, 550);
	glOrtho(-95,95,-95,95,150,550);
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	  
	glLoadIdentity();
	gluLookAt(pos[0], pos[1], pos[2], dir[0], dir[1], dir[2],0,0,-1);
}

void Light::updateMatrix()
{
	// Grab modelview and transformation matrices
	glGetDoublev(GL_MODELVIEW_MATRIX, MV);
	glGetDoublev(GL_PROJECTION_MATRIX, P);
}

void Light::use(int unit)
{
	const GLdouble bias[16] = {	
		0.5, 0.0, 0.0, 0.0, 
		0.0, 0.5, 0.0, 0.0,
		0.0, 0.0, 0.5, 0.0,
	0.5, 0.5, 0.5, 1.0};
	
	glActiveTextureARB(GL_TEXTURE0+unit);
	glBindTexture(GL_TEXTURE_2D, depth);
  
	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();	
	//glLoadMatrixd(bias);
	glLoadMatrixd(P);
	//glMultMatrixd(P);
	glMultMatrixd(MV);
	glMatrixMode(GL_MODELVIEW);
}

void Light::beginDraw()
{
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
	glUseProgramObjectARB(0);
	glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
	glClear(GL_DEPTH_BUFFER_BIT);
	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); 
	glEnable(GL_CULL_FACE);
	// Avoids self-shadowing
	glCullFace(GL_FRONT);

	// push the necessary matrices
	setAsPOV();
}
  
void Light::endDraw()
{
	updateMatrix();
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,0);
	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
	glMatrixMode(GL_PROJECTION);
	glPopMatrix(); 

	
	
	//glCullFace(GL_BACK);
	glDisable(GL_CULL_FACE);

}
